package org.lc.kcjxzj.service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.lc.kcjxzj.dto.CompareResultDTO;
import org.lc.kcjxzj.model.Kcjxzj;
import org.lc.kcjxzj.model.Result;
import org.lc.kcjxzj.repository.KcjxzjRepository;
import org.lc.kcjxzj.repository.ResultRepository;
import org.lc.kcjxzj.textcheck.AlgorithmDispatcher;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.StringUtils;

@Service
public class CompareService {
    
    private final KcjxzjRepository kcjxzjRepository;
    private final ResultRepository resultRepository;
    private final AlgorithmDispatcher algorithmDispatcher;
    private final PlatformTransactionManager transactionManager;
    
    // Thread pool size - adjust based on CPU cores
    private static final int DEFAULT_THREAD_POOL_SIZE = Runtime.getRuntime().availableProcessors();
    // Reduce batch size to avoid transaction timeouts
    private static final int DEFAULT_BATCH_SIZE = 50;
    
    @Value("${spring.datasource.hikari.maximum-pool-size:10}")
    private int maxPoolSize;
    
    public CompareService(
            KcjxzjRepository kcjxzjRepository,
            ResultRepository resultRepository,
            AlgorithmDispatcher algorithmDispatcher,
            PlatformTransactionManager transactionManager) {
        this.kcjxzjRepository = kcjxzjRepository;
        this.resultRepository = resultRepository;
        this.algorithmDispatcher = algorithmDispatcher;
        this.transactionManager = transactionManager;
    }

    public void fullCompare() {
        fullCompare(Math.min(DEFAULT_THREAD_POOL_SIZE, maxPoolSize - 1));
    }

    public void fullCompare(int threadCount) {
        // Step 1: Load all data in a single transaction and create detached copies
        List<Kcjxzj> allRecords = loadAllRecordsDetached();
        if (allRecords.isEmpty()) {
            System.out.println("No records to compare");
            return;
        }
        
        // Step 2: Clear existing results in a separate transaction
        clearExistingResults();
        
        int totalRecords = allRecords.size();
        System.out.println("Starting comparison of " + totalRecords + " records using " + threadCount + " threads");
        int totalComparisons = (totalRecords * (totalRecords - 1)) / 2;
        int batchSize = DEFAULT_BATCH_SIZE;
        int logInterval = Math.max(totalComparisons / 10, 100);
        
        // For progress reporting
        AtomicInteger completedComparisons = new AtomicInteger(0);
        
        // Create a thread-safe queue for result batches
        BlockingQueue<List<Result>> resultBatches = new LinkedBlockingQueue<>();
        
        // Create transaction template for consumer thread
        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        transactionTemplate.setTimeout(30); // 30 seconds timeout
        
        // Create and start result consumer thread
        Thread resultConsumer = new Thread(() -> {
            try {
                while (!Thread.currentThread().isInterrupted()) {
                    List<Result> batch = resultBatches.poll(500, TimeUnit.MILLISECONDS);
                    
                    if (batch != null && !batch.isEmpty()) {
                        try {
                            final List<Result> finalBatch = new ArrayList<>(batch);
                            // Use transaction template instead of annotation
                            transactionTemplate.execute(status -> {
                                try {
                                    resultRepository.saveAll(finalBatch);
                                    return null;
                                } catch (Exception e) {
                                    System.err.println("Error in transaction: " + e.getMessage());
                                    status.setRollbackOnly();
                                    return null;
                                }
                            });
                        } catch (Exception e) {
                            System.err.println("Error saving batch: " + e.getMessage());
                            // We don't attempt individual saves to avoid more connection issues
                        }
                    }
                    
                    // If we've been interrupted and the queue is empty, we're done
                    if (Thread.currentThread().isInterrupted() && resultBatches.isEmpty()) {
                        break;
                    }
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        resultConsumer.start();
        
        // Create progress monitor thread
        Thread progressMonitor = new Thread(() -> {
            try {
                while (!Thread.currentThread().isInterrupted()) {
                    int current = completedComparisons.get();
                    if (current % logInterval == 0 && current > 0) {
                        System.out.printf("Progress: %d/%d (%.1f%%)%n",
                            current, totalComparisons,
                            (current * 100.0 / totalComparisons));
                    }
                    Thread.sleep(2000); // Check progress every 2 seconds
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        progressMonitor.start();
        
        // Create thread pool
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        
        // Split work into tasks - with smaller chunks
        int chunkSize = totalRecords / (threadCount * 2);
        chunkSize = Math.max(chunkSize, 10); // Minimum 10 records per chunk
        
        for (int start = 0; start < totalRecords; start += chunkSize) {
            final int startIndex = start;
            final int endIndex = Math.min(start + chunkSize, totalRecords);
            
            executor.submit(() -> {
                List<Result> batchResults = new ArrayList<>(batchSize);
                
                for (int i = startIndex; i < endIndex; i++) {
                    for (int j = i + 1; j < totalRecords; j++) {
                        try {
                            Kcjxzj zjA = allRecords.get(i);
                            Kcjxzj zjB = allRecords.get(j);
                            Result result = compareRecords(zjA, zjB);
                            batchResults.add(result);
                            
                            int completed = completedComparisons.incrementAndGet();
                            
                            // When batch is full, submit it for saving
                            if (batchResults.size() >= batchSize) {
                                try {
                                    resultBatches.put(new ArrayList<>(batchResults));
                                    batchResults.clear();
                                } catch (InterruptedException e) {
                                    Thread.currentThread().interrupt();
                                    break;
                                }
                            }
                        } catch (Exception e) {
                            System.err.println("Error processing comparison: " + e.getMessage());
                        }
                    }
                }
                
                // Add any remaining results
                if (!batchResults.isEmpty()) {
                    try {
                        resultBatches.put(new ArrayList<>(batchResults));
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
            });
        }
        
        // Shutdown executor and wait for all tasks to complete
        executor.shutdown();
        try {
            // Use a timeout to ensure we don't wait forever
            boolean completed = executor.awaitTermination(24, TimeUnit.HOURS);
            if (!completed) {
                System.out.println("Executor timed out before all tasks completed");
            }
            
            // Stop progress monitor
            progressMonitor.interrupt();
            progressMonitor.join();
            
            // Wait for remaining results to be saved
            Thread.sleep(1000);
            resultConsumer.interrupt();
            resultConsumer.join(30000); // Wait up to 30 seconds for save to complete
            
            System.out.println("All comparison tasks completed: " + completedComparisons.get() + "/" + totalComparisons);
        } catch (InterruptedException e) {
            System.out.println("Comparison process was interrupted");
            Thread.currentThread().interrupt();
        }
        
        System.out.println("Comparison completed");
    }
    
    /**
     * Load all records in a single transaction and detach them
     */
    @Transactional(readOnly = true)
    public List<Kcjxzj> loadAllRecordsDetached() {
        try {
            System.out.println("Loading all records");
            List<Kcjxzj> records = kcjxzjRepository.findAll();
            System.out.println("Loaded " + records.size() + " records");
            return new ArrayList<>(records); // Create a detached copy
        } catch (Exception e) {
            System.err.println("Error loading records: " + e.getMessage());
            return new ArrayList<>();
        }
    }
    
    @Transactional
    public void clearExistingResults() {
        try {
            resultRepository.deleteAll();
            System.out.println("Existing results cleared");
        } catch (Exception e) {
            System.err.println("Error clearing results: " + e.getMessage());
            throw e;
        }
    }
    
    /**
     * Compute similarities without database operations
     */
    private Result compareRecords(Kcjxzj zjA, Kcjxzj zjB) {
        Result result = new Result();
        result.setZjA(zjA);
        result.setZjB(zjB);
        
        // 计算教学方法相似度
        if (StringUtils.hasText(zjA.getJxff()) && StringUtils.hasText(zjB.getJxff())) {
            result.setJxffSimilarity1(algorithmDispatcher.cosine(zjA.getJxff(), zjB.getJxff()));
            result.setJxffSimilarity2(algorithmDispatcher.jaccard(zjA.getJxff(), zjB.getJxff()));
            result.setJxffSimilarity3(algorithmDispatcher.levenshtein(zjA.getJxff(), zjB.getJxff()));
            result.setJxffSimilarity4(algorithmDispatcher.consecutive(zjA.getJxff(), zjB.getJxff()));
        }

        // 计算形成性考核分析相似度
        if (StringUtils.hasText(zjA.getXcxfx()) && StringUtils.hasText(zjB.getXcxfx())) {
            result.setXcxfxSimilarity1(algorithmDispatcher.cosine(zjA.getXcxfx(), zjB.getXcxfx()));
            result.setXcxfxSimilarity2(algorithmDispatcher.jaccard(zjA.getXcxfx(), zjB.getXcxfx()));
            result.setXcxfxSimilarity3(algorithmDispatcher.levenshtein(zjA.getXcxfx(), zjB.getXcxfx()));
            result.setXcxfxSimilarity4(algorithmDispatcher.consecutive(zjA.getXcxfx(), zjB.getXcxfx()));
        }

        // 计算试卷分析相似度
        if (StringUtils.hasText(zjA.getSjfx()) && StringUtils.hasText(zjB.getSjfx())) {
            result.setSjfxSimilarity1(algorithmDispatcher.cosine(zjA.getSjfx(), zjB.getSjfx()));
            result.setSjfxSimilarity2(algorithmDispatcher.jaccard(zjA.getSjfx(), zjB.getSjfx()));
            result.setSjfxSimilarity3(algorithmDispatcher.levenshtein(zjA.getSjfx(), zjB.getSjfx()));
            result.setSjfxSimilarity4(algorithmDispatcher.consecutive(zjA.getSjfx(), zjB.getSjfx()));
        }

        // 计算成绩分析相似度
        if (StringUtils.hasText(zjA.getCjfx()) && StringUtils.hasText(zjB.getCjfx())) {
            result.setCjfxSimilarity1(algorithmDispatcher.cosine(zjA.getCjfx(), zjB.getCjfx()));
            result.setCjfxSimilarity2(algorithmDispatcher.jaccard(zjA.getCjfx(), zjB.getCjfx()));
            result.setCjfxSimilarity3(algorithmDispatcher.levenshtein(zjA.getCjfx(), zjB.getCjfx()));
            result.setCjfxSimilarity4(algorithmDispatcher.consecutive(zjA.getCjfx(), zjB.getCjfx()));
        }

        // 计算总结相似度
        if (StringUtils.hasText(zjA.getZj()) && StringUtils.hasText(zjB.getZj())) {
            result.setZjSimilarity1(algorithmDispatcher.cosine(zjA.getZj(), zjB.getZj()));
            result.setZjSimilarity2(algorithmDispatcher.jaccard(zjA.getZj(), zjB.getZj()));
            result.setZjSimilarity3(algorithmDispatcher.levenshtein(zjA.getZj(), zjB.getZj()));
            result.setZjSimilarity4(algorithmDispatcher.consecutive(zjA.getZj(), zjB.getZj()));
        }

        // 计算意见建议相似度
        if (StringUtils.hasText(zjA.getYjjy()) && StringUtils.hasText(zjB.getYjjy())) {
            result.setYjjySimilarity1(algorithmDispatcher.cosine(zjA.getYjjy(), zjB.getYjjy()));
            result.setYjjySimilarity2(algorithmDispatcher.jaccard(zjA.getYjjy(), zjB.getYjjy()));
            result.setYjjySimilarity3(algorithmDispatcher.levenshtein(zjA.getYjjy(), zjB.getYjjy()));
            result.setYjjySimilarity4(algorithmDispatcher.consecutive(zjA.getYjjy(), zjB.getYjjy()));
        }

        return result;
    }

    public Map<String, CompareResultDTO> compareWithAll(Kcjxzj newRecord) {
        List<Kcjxzj> existingRecords = kcjxzjRepository.findAll();
        Map<String, CompareResultDTO> results = new HashMap<>();

        if (existingRecords.isEmpty()) {
            return results;
        }

        // 对每个部分，找出最相似的记录
        double maxJxffSim = 0.0;
        double maxXcxfxSim = 0.0;
        double maxSjfxSim = 0.0;
        double maxCjfxSim = 0.0;
        double maxZjSim = 0.0;
        double maxYjjySim = 0.0;

        Kcjxzj jxffMostSimilar = null;
        Kcjxzj xcxfxMostSimilar = null;
        Kcjxzj sjfxMostSimilar = null;
        Kcjxzj cjfxMostSimilar = null;
        Kcjxzj zjMostSimilar = null;
        Kcjxzj yjjyMostSimilar = null;

        for (Kcjxzj existing : existingRecords) {
            // 计算教学方法相似度
            if (StringUtils.hasText(newRecord.getJxff()) && StringUtils.hasText(existing.getJxff())) {
                double sim = algorithmDispatcher.consecutive(newRecord.getJxff(), existing.getJxff());
                if (sim > maxJxffSim) {
                    maxJxffSim = sim;
                    jxffMostSimilar = existing;
                }
            }

            // 计算形成性考核分析相似度
            if (StringUtils.hasText(newRecord.getXcxfx()) && StringUtils.hasText(existing.getXcxfx())) {
                double sim = algorithmDispatcher.consecutive(newRecord.getXcxfx(), existing.getXcxfx());
                if (sim > maxXcxfxSim) {
                    maxXcxfxSim = sim;
                    xcxfxMostSimilar = existing;
                }
            }

            // 计算试卷分析相似度
            if (StringUtils.hasText(newRecord.getSjfx()) && StringUtils.hasText(existing.getSjfx())) {
                double sim = algorithmDispatcher.consecutive(newRecord.getSjfx(), existing.getSjfx());
                if (sim > maxSjfxSim) {
                    maxSjfxSim = sim;
                    sjfxMostSimilar = existing;
                }
            }

            // 计算成绩分析相似度
            if (StringUtils.hasText(newRecord.getCjfx()) && StringUtils.hasText(existing.getCjfx())) {
                double sim = algorithmDispatcher.consecutive(newRecord.getCjfx(), existing.getCjfx());
                if (sim > maxCjfxSim) {
                    maxCjfxSim = sim;
                    cjfxMostSimilar = existing;
                }
            }

            // 计算总结相似度
            if (StringUtils.hasText(newRecord.getZj()) && StringUtils.hasText(existing.getZj())) {
                double sim = algorithmDispatcher.consecutive(newRecord.getZj(), existing.getZj());
                if (sim > maxZjSim) {
                    maxZjSim = sim;
                    zjMostSimilar = existing;
                }
            }

            // 计算意见建议相似度
            if (StringUtils.hasText(newRecord.getYjjy()) && StringUtils.hasText(existing.getYjjy())) {
                double sim = algorithmDispatcher.consecutive(newRecord.getYjjy(), existing.getYjjy());
                if (sim > maxYjjySim) {
                    maxYjjySim = sim;
                    yjjyMostSimilar = existing;
                }
            }
        }

        // 将结果存入Map
        results.put("jxff", new CompareResultDTO(maxJxffSim * 100, jxffMostSimilar));
        results.put("xcxfx", new CompareResultDTO(maxXcxfxSim * 100, xcxfxMostSimilar));
        results.put("sjfx", new CompareResultDTO(maxSjfxSim * 100, sjfxMostSimilar));
        results.put("cjfx", new CompareResultDTO(maxCjfxSim * 100, cjfxMostSimilar));
        results.put("zj", new CompareResultDTO(maxZjSim * 100, zjMostSimilar));
        results.put("yjjy", new CompareResultDTO(maxYjjySim * 100, yjjyMostSimilar));

        return results;
    }

    private Result compareAndSave(Kcjxzj zjA, Kcjxzj zjB) {
        return compareRecords(zjA, zjB);
    }
}